#include "kmer.h"
#include "libheader.h"

/*****************************************************************************/
/*                                                                           */
/* Constructor and Destructor                                                */
/*                                                                           */
/*****************************************************************************/
KMer::KMer(KMer* next, uint64_t pattern_code, int k_value){
  this->next=next;
  uint64_t rc_code=reverseComplement(pattern_code, k_value);
  if(rc_code<pattern_code){
    this->pattern_code=rc_code;
  } else {
    this->pattern_code=pattern_code;
  }
  adj_nucl_arr.reset();
  count=1;
  this->contig=NULL;
}

KMer::KMer(KMer* next, const char* pattern, int k_value){
  
  
  this->next=next;
  this->pattern_code=PattoCode(pattern, k_value);
  
  adj_nucl_arr.reset();
  count =1;
  this->contig=NULL;
}

KMer::~KMer(){}

/*****************************************************************************/
/*                                                                           */
/* Pub 1 Access Attributes                                                   */
/*                                                                           */
/*****************************************************************************/

//get pattern
uint64_t KMer::getPatCode(){
  return pattern_code;
}
uint64_t KMer::getPatCodeR(int k_value){
  return reverseComplement(pattern_code, k_value);
}

//get DNA
void KMer::getDNA(char* dna, int k_value){
  uint64_t mask=0x3ull;
  uint64_t tmp_code=pattern_code;
  
  for(int i=0;i<k_value;i++){
    switch(tmp_code >> (k_value - i - 1) *2 & mask){
      case 0:
        dna[i]='A';
        break;
      case 1:
        dna[i]='C';
        break;
      case 2:
        dna[i]='G';
        break;
      case 3:
        dna[i]='T';
        break;
    }
  }
  dna[k_value]=0;
}
void KMer::getDNAR(char* dna, int k_value){
  uint64_t mask=0x3ull;
  uint64_t tmp_code=pattern_code;
  
  for(int i=0;i<k_value;i++){
    switch(tmp_code >> i*2 & mask){
      case 0:
        dna[i]='T';
        break;
      case 1:
        dna[i]='G';
        break;
      case 2:
        dna[i]='C';
        break;
      case 3:
        dna[i]='A';
        break;
    }
  }
  dna[k_value]=0;
}


//set Next K mer
void KMer::setNext(uint64_t code, char nuclecide, bool is_exist){
  int pos=0;
  
  switch(nuclecide){
    case 'a':
    case 'A':
      pos=4;
      break;
    
    case 'c':
    case 'C':
      pos=5;
      break;
    
    case 'g':
    case 'G':
      pos=6;
      break;
    
    case 't':
    case 'T':
      pos=7;
      break;
  }
    
  if(code==this->pattern_code){
    adj_nucl_arr.set(pos, is_exist);
  } else {
    adj_nucl_arr.set(7-pos, is_exist);
  }
}

void KMer::setNext(uint64_t code, int nucl_int, bool is_exist){
  if(code==this->pattern_code){
    adj_nucl_arr.set(nucl_int+4, is_exist);
  } else {
    adj_nucl_arr.set(3-nucl_int, is_exist);
  }
}

void KMer::setPrev(uint64_t code, char nuclecide, bool is_exist){
  int pos=0;
  
  switch(nuclecide){
    case 'a':
    case 'A':
      pos=0;
      break;
    
    case 'c':
    case 'C':
      pos=1;
      break;
    
    case 'g':
    case 'G':
      pos=2;
      break;
    
    case 't':
    case 'T':
      pos=3;
      break;
  }
    
  if(code==pattern_code){
    adj_nucl_arr.set(pos, is_exist);
  } else {
    adj_nucl_arr.set(7-pos, is_exist);
  }
}

void KMer::setPrev(uint64_t code, int nucl_int, bool is_exist){    
  if(code==pattern_code){
    adj_nucl_arr.set(nucl_int, is_exist);
  } else {
    adj_nucl_arr.set(7-nucl_int, is_exist);
  }
}

//check if next K mer exist
bool KMer::checkNext(uint64_t code, char nuclecide){
  int nucl_int=0;
  
  switch(nuclecide){
    case 'a':
    case 'A':
      break;
  
    case 'c':
    case 'C':
      nucl_int=1;
      break;
    
    case 'g':
    case 'G':
      nucl_int=2;
      break;
    
    case 't':
    case 'T':
      nucl_int=3;
      break;
      
    default:
      cerr<<"In KMer::getNext(uint64_t code, char nuclecide, int k_value), \'"<<nuclecide<<"\' is not A,C,G or T"<<endl;
      break;
  }
  return KMer::checkNext(code, nucl_int);
}

bool KMer::checkNext(uint64_t code, int nucl_int){
  if(code==this->pattern_code){
    return adj_nucl_arr[nucl_int+4];
  } else {
    return adj_nucl_arr[3-nucl_int];
  }
}

bool KMer::checkPrev(uint64_t code, char nuclecide){
  int nucl_int=0;
  
  switch(nuclecide){
    case 'a':
    case 'A':
      break;
  
    case 'c':
    case 'C':
      nucl_int=1;
      break;
    
    case 'g':
    case 'G':
      nucl_int=2;
      break;
    
    case 't':
    case 'T':
      nucl_int=3;
      break;
      
    default:
      cerr<<"In KMer::getNext(uint64_t code, char nuclecide, int k_value), \'"<<nuclecide<<"\' is not A,C,G or T"<<endl;
      break;
  }
  return KMer::checkPrev(code, nucl_int);
}

bool KMer::checkPrev(uint64_t code, int nucl_int){
  if(code==this->pattern_code){
    return adj_nucl_arr[nucl_int];
  } else {
    return adj_nucl_arr[7-nucl_int];
  }
}

//get count
unsigned short KMer::getCount(){
  return this->count;
}

//add count
unsigned short KMer::addCount(unsigned short add_value){
  this->count+=add_value;
  return this->count;
}

//get Next
KMer* KMer::getNextKmer(){
  return next;
}

//set Next
void KMer::setNextKmer(KMer* next_kmer){
  this->next=next_kmer;
}

//get Adj arr
bitset<8> KMer::getAdjArr(){
  return adj_nucl_arr;
}
 
//check if in single path
bool KMer::isSingleNext(uint64_t code){
  if(code==this->pattern_code){
    return ((int)adj_nucl_arr[4]+(int)adj_nucl_arr[5]+(int)adj_nucl_arr[6]+(int)adj_nucl_arr[7]==1?true:false);
  } else {
    return ((int)adj_nucl_arr[0]+(int)adj_nucl_arr[1]+(int)adj_nucl_arr[2]+(int)adj_nucl_arr[3]==1?true:false);
  }
}

bool KMer::isSinglePrev(uint64_t code){
  if(code==this->pattern_code){
    return ((int)adj_nucl_arr[0]+(int)adj_nucl_arr[1]+(int)adj_nucl_arr[2]+(int)adj_nucl_arr[3]==1?true:false);
  } else {
    return ((int)adj_nucl_arr[4]+(int)adj_nucl_arr[5]+(int)adj_nucl_arr[6]+(int)adj_nucl_arr[7]==1?true:false);
  }
}

int KMer::getOutDeg(uint64_t code){
  if(code==this->pattern_code){
    return (int)adj_nucl_arr[4]+(int)adj_nucl_arr[5]+(int)adj_nucl_arr[6]+(int)adj_nucl_arr[7];
  } else {
    return (int)adj_nucl_arr[0]+(int)adj_nucl_arr[1]+(int)adj_nucl_arr[2]+(int)adj_nucl_arr[3];
  }
}
int KMer::getInDeg(uint64_t code){
  if(code==this->pattern_code){
    return (int)adj_nucl_arr[0]+(int)adj_nucl_arr[1]+(int)adj_nucl_arr[2]+(int)adj_nucl_arr[3];
  } else {
    return (int)adj_nucl_arr[4]+(int)adj_nucl_arr[5]+(int)adj_nucl_arr[6]+(int)adj_nucl_arr[7];
  }
}

//get corresponding contig
Contig* KMer::getContig(){
  return this->contig;
}

int KMer::setContig(Contig* contig){
  this->contig=contig;
  return 0;
}
/*****************************************************************************/
/*                                                                           */
/* Pub 2 Algorithms                                                          */
/*                                                                           */
/*****************************************************************************/

//get Next K mer
uint64_t KMer::getNext(uint64_t code, char nuclecide, int k_value){
  int nucl_int=0;
  
  switch(nuclecide){
    case 'a':
    case 'A':
      break;
  
    case 'c':
    case 'C':
      nucl_int=1;
      break;
    
    case 'g':
    case 'G':
      nucl_int=2;
      break;
    
    case 't':
    case 'T':
      nucl_int=3;
      break;
      
    default:
      cerr<<"In KMer::getNext(uint64_t code, char nuclecide, int k_value), \'"<<nuclecide<<"\' is not A,C,G or T"<<endl;
      break;
  }
  return KMer::getNext(code, nucl_int, k_value);
}

uint64_t KMer::getNext(uint64_t code, int nucl_int, int k_value){
  uint64_t mask=-1;
  mask=mask>>(64-k_value*2);

  return (((code << 2) | (uint64_t)nucl_int)) & mask;
/*  
  uint64_t next_code=(((code << 2) | (uint64_t)nucl_int)) & mask;
  uint64_t next_codeR=reverseComplement(next_code, k_value){
  
  if(next_code < next_codeR){
    return next_code;
  } else {
    return next_codeR;
  }
*/
}

uint64_t KMer::getPrev(uint64_t code, char nuclecide, int k_value){
  int nucl_int=0;
  
  switch(nuclecide){
    case 'a':
    case 'A':
      break;
  
    case 'c':
    case 'C':
      nucl_int=1;
      break;
    
    case 'g':
    case 'G':
      nucl_int=2;
      break;
    
    case 't':
    case 'T':
      nucl_int=3;
      break;
      
    default:
      cerr<<"In KMer::getPrev(uint64_t code, char nuclecide, int k_value), \'"<<nuclecide<<"\' is not A,C,G or T"<<endl;
      break;
  }
  return KMer::getPrev(code, nucl_int, k_value);
}

uint64_t KMer::getPrev(uint64_t code, int nucl_int, int k_value){
  uint64_t newbits=((uint64_t)nucl_int)<<(k_value-1)*2;

  return (code >> 2) | newbits;
/*  
  uint64_t prev_code=(code >> 2) | newbits;
  uint64_t prev_codeR=reverseComplement(prev_code, k_value){
  
  if(prev_code < prev_codeR){
    return prev_code;
  } else {
    return prev_codeR;
  }
*/
}

uint64_t KMer::PattoCode(const char* pattern, int k_value){
  uint64_t tmp_code=0;
  for(int i=0;i<k_value;i++){
    switch(pattern[i]){
      case 'a':
      case 'A':
      	tmp_code = tmp_code << 2;
      	break; 
      
      case 'c':
      case 'C':
      	tmp_code = tmp_code << 2;
      	tmp_code = tmp_code | 0x1ull;
      	break;
      
      case 'g':
      case 'G':
      	tmp_code = tmp_code << 2;
      	tmp_code = tmp_code | 0x2ull;
      	break;
      
      case 't':
      case 'T':
      	tmp_code = tmp_code << 2;
      	tmp_code = tmp_code | 0x3ull;
      	break;
      	
      default:
        cerr << "In KMer::PattoCode, the "<<i<<"-th position of k-mer \""<<pattern<<"\" is "<<pattern[i]<<endl;
        break;
    }
  }
  
  uint64_t rc_code=reverseComplement(tmp_code, k_value);
  if(rc_code<tmp_code){
    return rc_code;
  } else {
    return tmp_code;
  }
}

/*****************************************************************************/
/*                                                                           */
/* Pri 1 Algorithms                                                          */
/*                                                                           */
/*****************************************************************************/
uint64_t KMer::reverseComplement(uint64_t pattern, int k_value){
   uint64_t mask=0x3ull;
   uint64_t rc_pattern=0x0ull;
   for(int i=0;i<k_value;i++){
     rc_pattern=rc_pattern<<2;
     rc_pattern=rc_pattern|(0x3ull - (pattern & mask));
     pattern=pattern>>2;
   }
   return rc_pattern;
}

/*
int main(){
  char dna_pattern[]="AACCGGTTACGT";
  int k_value=strlen(dna_pattern);
  KMer kmer1(NULL, (const char*)&dna_pattern, k_value);
  
  cout<<"DNA = "<<dna_pattern<<endl;
  
  char tmp_pattern[k_value+1];
  kmer1.getDNA(tmp_pattern,k_value);
  cout<<"kmer1 =         "<<tmp_pattern<<endl;
  kmer1.getDNAR(tmp_pattern,k_value);
  cout<<"kmer1(rc) =     "<<tmp_pattern<<endl;
  cout<<"kmer1 code =    "<<kmer1.getPatCode()<<endl;
  cout<<"kmer1 rc code = "<<kmer1.getPatCodeR(k_value)<<endl;
  
  cout<<"setNext(pattern,'A',true)"<<endl;
  kmer1.setNext(kmer1.getPatCode(),'A',true);
  cout<<"setNext(patternR,'C',true)"<<endl;
  kmer1.setNext(kmer1.getPatCodeR(k_value),'C',true);
  
  cout<<"kmer1.checkNext(pattern,'A') = "<<(kmer1.checkNext(kmer1.getPatCode(),'A')?"true":"false")<<endl;
  cout<<"kmer1.checkPrev(patternR,'T') = "<<(kmer1.checkPrev(kmer1.getPatCodeR(k_value),'T')?"true":"false")<<endl;
  cout<<"kmer1.checkPrev(pattern,'G') = "<<(kmer1.checkPrev(kmer1.getPatCode(),'G')?"true":"false")<<endl;
  cout<<"kmer1.checkNext(patternR,'C') = "<<(kmer1.checkNext(kmer1.getPatCodeR(k_value),'C')?"true":"false")<<endl;
  cout<<"kmer1.checkNext(pattern,'C') = "<<(kmer1.checkNext(kmer1.getPatCode(),'C')?"true":"false")<<endl;
  
  cout<<"setPrev(patternR,'T',false)"<<endl;
  kmer1.setPrev(kmer1.getPatCodeR(k_value),'T',false);
  cout<<"setPrev(patternR,'G',true)"<<endl;
  kmer1.setPrev(kmer1.getPatCodeR(k_value),'G',true);
  
  cout<<"kmer1.checkNext(pattern,'A') = "<<(kmer1.checkNext(kmer1.getPatCode(),'A')?"true":"false")<<endl;
  cout<<"kmer1.checkPrev(patternR,'T') = "<<(kmer1.checkPrev(kmer1.getPatCodeR(k_value),'T')?"true":"false")<<endl;
  cout<<"kmer1.checkPrev(pattern,'G') = "<<(kmer1.checkPrev(kmer1.getPatCode(),'G')?"true":"false")<<endl;
  cout<<"kmer1.checkNext(patternR,'C') = "<<(kmer1.checkNext(kmer1.getPatCodeR(k_value),'C')?"true":"false")<<endl;
  cout<<"kmer1.checkNext(pattern,'C') = "<<(kmer1.checkNext(kmer1.getPatCode(),'C')?"true":"false")<<endl;
  
  KMer kmer2(NULL, kmer1.getPatCodeR(k_value), k_value);
  kmer2.getDNA(tmp_pattern,k_value);
  cout<<"kmer2 =         "<<tmp_pattern<<endl;
  kmer2.getDNAR(tmp_pattern,k_value);
  cout<<"kmer2(rc) =     "<<tmp_pattern<<endl;
  cout<<"kmer2 code =    "<<kmer2.getPatCode()<<endl;
  cout<<"kmer2 rc code = "<<kmer2.getPatCodeR(k_value)<<endl;
  
  cout<<"KMer::getNext(kmer1.getPatCode(), 'C', k_value);"<<endl;
  uint64_t code3=KMer::getNext(kmer1.getPatCode(), 'C', k_value);
  
  KMer kmer3(NULL, code3, k_value);
  kmer3.getDNA(tmp_pattern,k_value);
  cout<<"kmer3 =         "<<tmp_pattern<<endl;
  kmer3.getDNAR(tmp_pattern,k_value);
  cout<<"kmer3(rc) =     "<<tmp_pattern<<endl;
  cout<<"kmer3 code =    "<<kmer3.getPatCode()<<endl;
  cout<<"kmer3 rc code = "<<kmer3.getPatCodeR(k_value)<<endl;
  
  cout<<"KMer::getPrev(kmer1.getPatCode(), 'T', k_value);"<<endl;
  uint64_t code4=KMer::getPrev(kmer1.getPatCode(), 'T', k_value);
  
  KMer kmer4(NULL, code4, k_value);
  kmer4.getDNA(tmp_pattern,k_value);
  cout<<"kmer4 =         "<<tmp_pattern<<endl;
  kmer4.getDNAR(tmp_pattern,k_value);
  cout<<"kmer4(rc) =     "<<tmp_pattern<<endl;
  cout<<"kmer4 code =    "<<kmer4.getPatCode()<<endl;
  cout<<"kmer4 rc code = "<<kmer4.getPatCodeR(k_value)<<endl;
  
  
  
  return 0;
}
*/